#pragma once

#include "Lux\point.h"
#include "Lux\triangle.h"
#include "Lux\bvhaccel.h"

namespace DLLx {

using namespace System::Collections::Generic;
using namespace System::IO;

[Serializable()]
public ref struct TriInfo
{
public:
	int v1,v2,v3;
	int id;
};
[Serializable()]
public ref struct VertInfo
{
public:
	float x,y,z;
	VertInfo()
	{
	}
	VertInfo(VertInfo^ v)
	{
		x = v->x;
		y = v->y;
		z = v->z;
	}
	VertInfo(float a, float b, float c) {
		x = a;
		y = b;
		z = c;
	};
};
[Serializable()]
public ref struct Box {
public:
	VertInfo^ pMin;
	VertInfo^  pMax;
	Box(VertInfo^ min, VertInfo^ max) 
	{
		pMin = gcnew VertInfo(min);
		pMax  = gcnew VertInfo(max);
	}
};
[Serializable()]
public ref class BVHTreeNode {
public:
	Box^ bbox;
	int primitive;
	BVHTreeNode^ leftChild;
	BVHTreeNode^ rightSibling;
};
[Serializable()]
public ref struct BVHAccelNode {
	Box^ bbox;
	unsigned int primitive;
	unsigned int skipIndex;
};
public ref class TriangleData 
{
public:
	Triangle* triangles;
	int nTri;
	Point* points;
	int nVert;

	TriangleData(array<TriInfo^>^ tri, array<VertInfo^>^ verts)
	{
		nTri = tri->Length;
		nVert = verts->Length;
		triangles = new Triangle[nTri];
		for(int i=0;i<nTri;i++)
		{
			triangles[i] = Triangle(tri[i]->v1,tri[i]->v2,tri[i]->v3);			
		}
		points = new Point[nVert];
		for(int i=0;i<nVert;i++)
		{
			points[i] = Point(verts[i]->x,verts[i]->y, verts[i]->z);
		}
	}


	~TriangleData()
	{
		delete points;
		delete triangles;

	}
};

public ref class BBoxData
{
public:
		BBox* boxes;
		int nBox;

		BBoxData(array<Box^>^ bbx)
		{
			nBox = bbx->Length;
			boxes = new BBox[nBox];
			for(int i=0;i<nBox;i++)
			{
				boxes[i] = BBox(Point(bbx[i]->pMin->x,bbx[i]->pMin->y, bbx[i]->pMin->z),
								Point(bbx[i]->pMax->x,bbx[i]->pMax->y, bbx[i]->pMax->z));
			}
		}
	
		~BBoxData()
		{
			delete boxes;
		}
};



public ref class BVHHandle : public IDisposable
{
public:
	BVHHandle(TriangleData^ data, const unsigned int treeType, const int csamples, const int icost,
		const int tcost, const float ebonus )
	{
		accell = new BVHAccel(data->nVert,data->nTri, data->triangles, data->points,
								treeType, csamples, icost,
								tcost, ebonus);
	}

	List<BVHAccelNode^>^ GetData()
	{
		List<BVHAccelNode^>^ list = gcnew List<BVHAccelNode^>(accell->nNodes);
		for(unsigned int i=0;i<accell->nNodes;i++) {
			BVHAccelArrayNode node = accell->bvhTree[i];
			BVHAccelNode^ resNode = gcnew BVHAccelNode();
			resNode->primitive = node.primitive;
			resNode->skipIndex = node.skipIndex;
			resNode->bbox =gcnew Box(gcnew VertInfo(node.bbox.pMin.x, node.bbox.pMin.y, node.bbox.pMin.z),
									 gcnew VertInfo(node.bbox.pMax.x, node.bbox.pMax.y, node.bbox.pMax.z));

			list->Add(resNode);
		}
		return list;
	}

	static List<BVHAccelNode^>^ LoadData(String^ str)
	{
		FileStream^ wrStream  = gcnew FileStream(str ,FileMode::Open);
		BinaryReader^ reader  = gcnew BinaryReader(wrStream);
		try {

			unsigned int nodes = reader->ReadUInt32();
			List<BVHAccelNode^>^ list = gcnew List<BVHAccelNode^>(nodes);

			for(unsigned int i=0;i<nodes;i++) {
				//BVHAccelArrayNode node = accell->bvhTree[i];
				BVHAccelNode^ node = gcnew BVHAccelNode();				
				float pMinx = reader->ReadSingle();
				float pMiny = reader->ReadSingle();
				float pMinz = reader->ReadSingle();

				float pMaxx = reader->ReadSingle();
				float pMaxy = reader->ReadSingle();
				float pMaxz = reader->ReadSingle();
				node->bbox = gcnew Box( gcnew VertInfo( pMinx, pMiny, pMinz),
										gcnew VertInfo(	pMaxx, pMaxy, pMaxz));


				
				node->primitive =  reader->ReadUInt32();
				node->skipIndex =  reader->ReadUInt32();
				list->Add(node);

			}
			return list;
		}
		finally {
			reader->Close();
			wrStream->Close();
		}
	}

	void SaveData(String^ str)
	{
		FileStream^ wrStream  = gcnew FileStream(str,FileMode::OpenOrCreate);
		BinaryWriter^ writer  = gcnew BinaryWriter(wrStream);
		try {

			writer->Write(accell->nNodes);

			for(unsigned int i=0;i<accell->nNodes;i++) {
				BVHAccelArrayNode node = accell->bvhTree[i];
				writer->Write(node.bbox.pMin.x);
				writer->Write(node.bbox.pMin.y);
				writer->Write(node.bbox.pMin.z);

				writer->Write(node.bbox.pMax.x);
				writer->Write(node.bbox.pMax.y);
				writer->Write(node.bbox.pMax.z);
				
				writer->Write(node.primitive);
				writer->Write(node.skipIndex);
			}
		}
		finally {
			writer->Close();
			wrStream->Close();
		}
	}


	BVHTreeNode^ GetCPUData()
	{
		return this->GetTree(*accell->debugTree);
	}

private:
	BVHTreeNode^ GetTree(BVHAccelTreeNode node)
	{
		
		BVHTreeNode^ rNode = gcnew BVHTreeNode();
		rNode->bbox = gcnew Box(gcnew VertInfo(node.bbox.pMin.x, node.bbox.pMin.y, node.bbox.pMin.z),
									 gcnew VertInfo(node.bbox.pMax.x, node.bbox.pMax.y, node.bbox.pMax.z));
		rNode->primitive = node.primitive;
		
		if (node.leftChild != NULL) 
		{
			rNode->leftChild = GetTree(*node.leftChild);
		}

		if (node.rightSibling != NULL) 
		{
			rNode->rightSibling = this->GetTree(*node.rightSibling);
		}
		return rNode;
	}

	~BVHHandle()
	{
		delete accell;
	}

private:
	BVHAccel* accell;

};


public ref class TopBVHHandle : public IDisposable
{
public:
	TopBVHHandle( BBoxData^ data, const unsigned int treeType, const int csamples, const int icost,
		const int tcost, const float ebonus )
	{
		accell = new TopLevelBVHAccel(data->nBox,data->boxes,
								treeType, csamples, icost,
								tcost, ebonus);
	}

	List<BVHAccelNode^>^ GetData()
	{
		List<BVHAccelNode^>^ list = gcnew List<BVHAccelNode^>(accell->nNodes);
		for(unsigned int i=0;i<accell->nNodes;i++) {
			BVHAccelArrayNode node = accell->bvhTree[i];
			BVHAccelNode^ resNode = gcnew BVHAccelNode();
			resNode->primitive = node.primitive;
			resNode->skipIndex = node.skipIndex;
			resNode->bbox =gcnew Box(gcnew VertInfo(node.bbox.pMin.x, node.bbox.pMin.y, node.bbox.pMin.z),
									 gcnew VertInfo(node.bbox.pMax.x, node.bbox.pMax.y, node.bbox.pMax.z));

			list->Add(resNode);
		}
		return list;
	}

	static List<BVHAccelNode^>^ LoadData(String^ str)
	{
		FileStream^ wrStream  = gcnew FileStream(str ,FileMode::Open);
		BinaryReader^ reader  = gcnew BinaryReader(wrStream);
		try {

			unsigned int nodes = reader->ReadUInt32();
			List<BVHAccelNode^>^ list = gcnew List<BVHAccelNode^>(nodes);

			for(unsigned int i=0;i<nodes;i++) {
				//BVHAccelArrayNode node = accell->bvhTree[i];
				BVHAccelNode^ node = gcnew BVHAccelNode();				
				float pMinx = reader->ReadSingle();
				float pMiny = reader->ReadSingle();
				float pMinz = reader->ReadSingle();

				float pMaxx = reader->ReadSingle();
				float pMaxy = reader->ReadSingle();
				float pMaxz = reader->ReadSingle();
				node->bbox = gcnew Box( gcnew VertInfo( pMinx, pMiny, pMinz),
										gcnew VertInfo(	pMaxx, pMaxy, pMaxz));


				
				node->primitive =  reader->ReadUInt32();
				node->skipIndex =  reader->ReadUInt32();
				list->Add(node);

			}
			return list;
		}
		finally {
			reader->Close();
			wrStream->Close();
		}
	}

	void SaveData(String^ str)
	{
		FileStream^ wrStream  = gcnew FileStream(str,FileMode::OpenOrCreate);
		BinaryWriter^ writer  = gcnew BinaryWriter(wrStream);
		try {

			writer->Write(accell->nNodes);

			for(unsigned int i=0;i<accell->nNodes;i++) {
				BVHAccelArrayNode node = accell->bvhTree[i];
				writer->Write(node.bbox.pMin.x);
				writer->Write(node.bbox.pMin.y);
				writer->Write(node.bbox.pMin.z);

				writer->Write(node.bbox.pMax.x);
				writer->Write(node.bbox.pMax.y);
				writer->Write(node.bbox.pMax.z);
				
				writer->Write(node.primitive);
				writer->Write(node.skipIndex);
			}
		}
		finally {
			writer->Close();
			wrStream->Close();
		}
	}


	BVHTreeNode^ GetCPUData()
	{
		return this->GetTree(*accell->debugTree);
	}

private:
	BVHTreeNode^ GetTree(BVHAccelTreeNode node)
	{
		
		BVHTreeNode^ rNode = gcnew BVHTreeNode();
		rNode->bbox = gcnew Box(gcnew VertInfo(node.bbox.pMin.x, node.bbox.pMin.y, node.bbox.pMin.z),
									 gcnew VertInfo(node.bbox.pMax.x, node.bbox.pMax.y, node.bbox.pMax.z));
		rNode->primitive = node.primitive;
		
		if (node.leftChild != NULL) 
		{
			rNode->leftChild = GetTree(*node.leftChild);
		}

		if (node.rightSibling != NULL) 
		{
			rNode->rightSibling = this->GetTree(*node.rightSibling);
		}
		return rNode;
	}

	~TopBVHHandle()
	{
		delete accell;
	}

private:
	TopLevelBVHAccel* accell;

};
}